import sys
import time
import struct

from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                             QHBoxLayout, QPushButton, QComboBox, QPlainTextEdit,
                             QGroupBox, QLabel)
from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QColor, QTextCharFormat, QFont

import serial
import serial.tools.list_ports


# =============================================================================
# 协议核心：Modbus CRC16 校验 (与之前版本相同)
# =============================================================================
def modbus_crc16(data: bytes) -> bytes:
    crc = 0xFFFF
    polynomial = 0xA001
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ polynomial
            else:
                crc >>= 1
    return struct.pack('<H', crc)


# =============================================================================
# 串口工作线程 (PyQt 推荐的多线程方式)
# =============================================================================
class SerialWorker(QObject):
    """
    将串口读操作移至此工作类，以避免阻塞GUI主线程。
    """
    # 定义信号
    data_received = pyqtSignal(bytes)
    error_occurred = pyqtSignal(str)

    def __init__(self, serial_instance):
        super().__init__()
        self.serial_port = serial_instance
        self._is_running = True

    @pyqtSlot()
    def run(self):
        """在线程中运行的主循环"""
        buffer = bytearray()
        while self._is_running:
            try:
                if not self.serial_port.is_open:
                    break  # 如果串口被关闭，则退出

                data = self.serial_port.read(self.serial_port.in_waiting or 1)
                if data:
                    buffer.extend(data)
                    while len(buffer) >= 5:
                        if buffer[0] != 0x0A:  # DEVICE_ADDRESS
                            buffer.pop(0)
                            continue

                        payload_len = buffer[2]
                        frame_len = 1 + 1 + 1 + payload_len + 2

                        if len(buffer) >= frame_len:
                            frame = buffer[:frame_len]

                            frame_data = frame[:-2]
                            crc_received = frame[-2:]
                            crc_calculated = modbus_crc16(frame_data)

                            if crc_received == crc_calculated:
                                self.data_received.emit(bytes(frame))  # 发射信号
                                buffer = buffer[frame_len:]
                            else:
                                self.error_occurred.emit(f"CRC校验失败: 收到{crc_received.hex()} vs 计算{crc_calculated.hex()}")
                                buffer.pop(0)
                        else:
                            break  # 数据不完整，等待
            except serial.SerialException as e:
                self.error_occurred.emit(f"串口读取错误: {e}")
                self._is_running = False

            # 短暂休眠，避免CPU占用过高
            time.sleep(0.01)

    def stop(self):
        self._is_running = False


# =============================================================================
# 主窗口类
# =============================================================================
class SerialProtocolApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("温度传感器上位机")
        self.setGeometry(100, 100, 800, 600)

        # --- 成员变量 ---
        self.serial_port = serial.Serial()
        self.serial_thread = None
        self.serial_worker = None

        self.DEVICE_ADDRESS = 0x0A

        # --- 初始化UI ---
        self._init_ui()
        self.update_port_list()

    def _init_ui(self):
        # --- 主布局 ---
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout(main_widget)

        # --- 连接控制区 ---
        conn_groupbox = QGroupBox("串口设置")
        conn_layout = QHBoxLayout()
        conn_groupbox.setLayout(conn_layout)

        conn_layout.addWidget(QLabel("端口:"))
        self.port_combobox = QComboBox()
        conn_layout.addWidget(self.port_combobox)

        self.refresh_button = QPushButton("刷新")
        self.refresh_button.clicked.connect(self.update_port_list)
        conn_layout.addWidget(self.refresh_button)

        self.connect_button = QPushButton("连接")
        self.connect_button.setCheckable(True)
        self.connect_button.clicked.connect(self.connect_disconnect)
        conn_layout.addWidget(self.connect_button)
        conn_layout.addStretch()

        # --- 命令发送区 ---
        cmd_groupbox = QGroupBox("命令发送")
        cmd_layout = QHBoxLayout()
        cmd_groupbox.setLayout(cmd_layout)

        test_conn_button = QPushButton("测试连接 ('z')")
        test_conn_button.clicked.connect(lambda: self.send_command(b'z'))
        cmd_layout.addWidget(test_conn_button)

        read_uid_button = QPushButton("读取UID ('uu')")
        read_uid_button.clicked.connect(lambda: self.send_command(b'uu'))
        cmd_layout.addWidget(read_uid_button)

        read_status_button = QPushButton("读取系统状态 ('az')")
        read_status_button.clicked.connect(lambda: self.send_command(b'az'))
        cmd_layout.addWidget(read_status_button)

        read_pd_vol_button = QPushButton("读取PD电压 ('caz')")
        read_pd_vol_button.clicked.connect(lambda: self.send_command(b'caz'))
        cmd_layout.addWidget(read_pd_vol_button)
        cmd_layout.addStretch()

        # --- 日志显示区 ---
        log_groupbox = QGroupBox("数据日志")
        log_layout = QVBoxLayout()
        log_groupbox.setLayout(log_layout)

        self.log_text_edit = QPlainTextEdit()
        self.log_text_edit.setReadOnly(True)
        self.log_text_edit.setFont(QFont("Courier New", 10))
        log_layout.addWidget(self.log_text_edit)

        # --- 将所有组件添加到主布局 ---
        main_layout.addWidget(conn_groupbox)
        main_layout.addWidget(cmd_groupbox)
        main_layout.addWidget(log_groupbox)

    def log_message(self, message, color="black"):
        timestamp = time.strftime('%H:%M:%S')
        formatted_message = f'<span style="color: {color};">[{timestamp}] {message}</span>'
        self.log_text_edit.appendHtml(formatted_message)

    def update_port_list(self):
        self.port_combobox.clear()
        ports = [port.device for port in serial.tools.list_ports.comports()]
        self.port_combobox.addItems(ports)

    def connect_disconnect(self, checked):
        if checked:
            port_name = self.port_combobox.currentText()
            if not port_name:
                self.log_message("错误: 未选择串口！", "red")
                self.connect_button.setChecked(False)
                return

            self.serial_port.port = port_name
            self.serial_port.baudrate = 115200
            self.serial_port.timeout = 0.1

            try:
                self.serial_port.open()
                self.log_message(f"已连接到 {port_name}", "green")
                self.connect_button.setText("断开")

                # 创建并启动工作线程
                self.serial_thread = QThread()
                self.serial_worker = SerialWorker(self.serial_port)
                self.serial_worker.moveToThread(self.serial_thread)

                # 连接信号和槽
                self.serial_thread.started.connect(self.serial_worker.run)
                self.serial_worker.data_received.connect(self.handle_received_frame)
                self.serial_worker.error_occurred.connect(lambda msg: self.log_message(f"错误: {msg}", "red"))

                self.serial_thread.start()

            except serial.SerialException as e:
                self.log_message(f"连接失败: {e}", "red")
                self.connect_button.setChecked(False)
        else:
            if self.serial_port.is_open:
                self.serial_worker.stop()
                self.serial_thread.quit()
                self.serial_thread.wait()
                self.serial_port.close()
                self.log_message("串口已断开", "black")

            self.connect_button.setText("连接")

    def send_command(self, payload: bytes):
        if not self.serial_port.is_open:
            self.log_message("错误: 请先连接串口！", "red")
            return

        modbus_data = b'g' + payload
        frame_without_crc = struct.pack('B', self.DEVICE_ADDRESS) + modbus_data
        full_frame = frame_without_crc + modbus_crc16(frame_without_crc)

        try:
            self.serial_port.write(full_frame)
            self.log_message(f"发送 -> {full_frame.hex(' ').upper()}", "blue")
        except serial.SerialException as e:
            self.log_message(f"发送失败: {e}", "red")
            self.connect_button.setChecked(False)  # 触发断开
            self.connect_disconnect(False)

    @pyqtSlot(bytes)
    def handle_received_frame(self, frame: bytes):
        print(frame)
        self.log_message(f"接收 <- {frame.hex(' ').upper()}", "darkgreen")
        self.parse_received_frame(frame)

    # =========================================================================
    # 完整数据解析 (逐行对应C#中的 commRecvFunc)
    # =========================================================================
    def parse_received_frame(self, frame: bytes):
        # frame[0]: 设备地址
        # frame[1]: 功能码 ('g')
        # frame[2]: 后续负载长度
        # frame[3:]: 数据负载 (应用层)

        app_payload = frame[3:-2]  # 提取应用层数据

        if not app_payload:
            self.log_message("警告: 收到空的应用层数据", "orange")
            return

        main_cmd = chr(app_payload[0])

        try:
            log_details = ""
            # ---------------------------------------------------------------------
            # switch (data[3]) -> case (byte)'a':
            # ---------------------------------------------------------------------
            if main_cmd == 'a':
                sub_cmd_a = chr(app_payload[1])
                if sub_cmd_a == 'b':
                    sub_cmd_b = chr(app_payload[2])
                    if sub_cmd_b == 'f':
                        # 解析7个浮点数
                        try:
                            vals = struct.unpack_from('<7f', app_payload, 3)
                            log_details = (f"  解析 [总线测量 'abf']:\n"
                                           f"    原始值: [{vals[0]:.0f}, {vals[1]:.0f}], cur={vals[2]:.2f}A, vol={vals[3]:.3f}V\n"
                                           f"    tick={vals[4]:.0f}, watchdog={vals[5]:.0f}, systick={vals[6]:.0f}")
                        except struct.error:
                            log_details = "  解析 [总线测量 'abf'] 失败: 数据长度不足"
                elif sub_cmd_a == 'z':
                    # 解析系统状态 (这是我们之前详细分析过的复杂包)
                    try:
                        # 5个bool(byte)
                        en = "on" if app_payload[2] > 0 else "off"
                        isopenloop = "openLoop" if app_payload[3] > 0 else "closedLoop"
                        mode = "voltage" if app_payload[4] > 0 else "current"
                        autorun = app_payload[5]
                        plimitEn = app_payload[6]
                        # 2个UInt32
                        fault, faultmask = struct.unpack_from('<2I', app_payload, 7)
                        # 18个float
                        f = struct.unpack_from('<18f', app_payload, 15)
                        log_details = (
                            f"  解析 [系统状态 'az']:\n"
                            f"  -------------------------------\n"
                            f"    系统: 输出{en}, {isopenloop}, {mode} mode, autorun={autorun}, plimitEn={plimitEn}\n"
                            f"    错误: fault=0x{fault:08X}, faultMask=0x{faultmask:08X}, faultData={f[0]:.6f}\n"
                            f"    控制: duty={f[1]:.2f}%, povc={f[2]:.2f}, vovc={f[3]:.2f}, iovc={f[4]:.2f}\n"
                            f"    设置: ref poSet={f[5]:.2f}, voSet={f[6]:.2f}, ioSet={f[7]:.2f}\n"
                            f"    监测: po={f[8]:.2f}W, vo={f[9]:.2f}V, io={f[10]:.2f}A, vin={f[11]:.2f}V\n"
                            f"    保护: ovp={f[15]:.1f}V, uvp={f[16]:.1f}V, oc={f[17]:.1f}A\n"
                            f"    时间: calc_time={f[12]:.1f}us, Tick={f[13]:.0f}, En_Tick={f[14]:.0f}\n"
                            f"  -------------------------------"
                        )
                    except struct.error:
                        log_details = "  解析 [系统状态 'az'] 失败: 数据长度不足"

            # ---------------------------------------------------------------------
            # switch (data[3]) -> case (byte)'c':
            # ---------------------------------------------------------------------
            elif main_cmd == 'c':
                sub_cmd = chr(app_payload[1])
                if sub_cmd == 'a' and chr(app_payload[2]) == 'z':
                    try:
                        voltages = struct.unpack_from('<24f', app_payload, 3)
                        log_details = "  解析 [PD电压 'caz']:\n"
                        for i in range(12):
                            log_details += f"    CH{i + 1:02d}: {voltages[i]:.3f}V | Cal: {voltages[i + 12]:.3f}V\n"
                    except struct.error:
                        log_details = "  解析 [PD电压 'caz'] 失败: 数据长度不足"
                elif sub_cmd == 'e':
                    en1 = "True" if app_payload[3] == 1 else "False"
                    log_details = f"  解析 [Enable 'ce_']:\n    En = {en1}"
                elif sub_cmd == 'g' and chr(app_payload[2]) == 's':
                    code = app_payload[3]
                    if code == 0:
                        log_details = "  解析 [Flash保存 'cgs']:\n    flash save done."
                    else:
                        log_details = f"  解析 [Flash保存 'cgs']:\n    flash save error, code : {0 - code}"
                elif sub_cmd == 'r':
                    op = "read" if chr(app_payload[2]) == 'r' else "write"
                    addr, val = struct.unpack_from('<2I', app_payload, 3)
                    log_details = f"  解析 [外设读写 'cr_']:\n    Peripheral {op}: addr = 0x{addr:08X}, value = 0x{val:08X}"
                elif sub_cmd == 'p':
                    mask = app_payload[3]
                    log_details = f"  解析 [模拟电源 'cp_']:\n    Analog Power Supply En : 0x{mask:02X}"
                elif sub_cmd == 't':  # AD4695
                    ad_sub_cmd = chr(app_payload[2])
                    if ad_sub_cmd in 'rs':
                        reg, val = struct.unpack_from('<2B', app_payload, 3)
                        log_details = f"  解析 [AD4695寄存器读写 'ct{ad_sub_cmd}']:\n    register 0x{reg:02X} = 0x{val:02X}"
                    elif ad_sub_cmd == 'a':
                        log_details = "  解析 [AD4695复位 'cta']:\n    AD4695 reset"
                    elif ad_sub_cmd == 'c':
                        log_details = "  解析 [AD4695配置 'ctc']:\n    AD4695 config"
                    elif ad_sub_cmd == 'e':
                        isinital = app_payload[3] > 0
                        iscnvmode = "conv" if app_payload[4] > 0 else "register"
                        chan, channum = struct.unpack_from('<2B', app_payload, 5)
                        log_details = f"  解析 [AD4695状态 'cte']:\n    status: initial={isinital}, mode={iscnvmode}, channel={chan}, channel_num={channum}"
                    elif ad_sub_cmd == 'f':
                        iscnvmode = "conversion mode" if app_payload[4] > 0 else "register mode"
                        log_details = f"  解析 [AD4695模式 'ctf']:\n    mode: {iscnvmode}"
                    elif ad_sub_cmd == 'g':
                        nextchan = app_payload[3]
                        result = struct.unpack_from('<I', app_payload, 4)[0]
                        log_details = f"  解析 [AD4695转换读取 'ctg']:\n    next channel {nextchan}, result 0x{result:08X}"

            # ---------------------------------------------------------------------
            # switch (data[3]) -> case (byte)'v':
            # ---------------------------------------------------------------------
            elif main_cmd == 'v':
                try:
                    addr, alen = struct.unpack_from('<2I', app_payload, 1)
                    log_details = f"  解析 [寄存器数据 'v']:\n    receive register data at address:0x{addr:08X}, len={alen}:"
                    data_start_index = 9
                    for i in range(alen):
                        if i % 4 == 0:
                            log_details += "\n    "
                        val = struct.unpack_from('<I', app_payload, data_start_index + i * 4)[0]
                        log_details += f"0x{val:08X}, "
                except struct.error:
                    log_details = "  解析 [寄存器数据 'v'] 失败: 数据长度不足"

            # ---------------------------------------------------------------------
            # switch (data[3]) -> case (byte)'u': (UID & IAP)
            # ---------------------------------------------------------------------
            elif main_cmd == 'u':
                sub_cmd = chr(app_payload[1])
                if sub_cmd == 'u':  # UID read
                    # C#代码通过位移手动解析大端数据，这里我们直接用'>'指定大端
                    try:
                        uid0, uid1, uid2 = struct.unpack_from('>3I', app_payload, 2)
                        log_details = f"  解析 [UID读取 'uu']:\n    UID: {uid0:08X}_{uid1:08X}_{uid2:08X}"
                    except struct.error:
                        log_details = "  解析 [UID读取 'uu'] 失败: 数据长度不足"
                elif sub_cmd == 'p':  # Protection
                    prot_sub_cmd = chr(app_payload[2])
                    if prot_sub_cmd == 'r':
                        p_mask = app_payload[3]
                        levels = []
                        if (p_mask & 0x01): levels.append("1")
                        if (p_mask & 0x02): levels.append("2")
                        if (p_mask & 0x04): levels.append("3")
                        level_str = ", ".join(levels) if levels else "0"
                        log_details = f"  解析 [读保护 'upr']:\n    chip data protection level {level_str}"
                    elif prot_sub_cmd == 'c':
                        log_details = "  解析 [解锁保护 'upc']:\n    chip data protection unlock done"
                    elif prot_sub_cmd == 'e':
                        level = app_payload[3]
                        log_details = f"  解析 [使能保护 'upe']:\n    chip data protection enable : 0x{level:X2}"
                    elif prot_sub_cmd == 'd' and chr(app_payload[3]) == 'r':
                        key = app_payload[4:12].hex(' ').upper()
                        log_details = f"  解析 [读保护密钥 'updr']:\n    level 1 key : [{key}]"
                elif sub_cmd == 'o':  # IAP
                    iap_sub_cmd = chr(app_payload[2])
                    flag = chr(app_payload[3])
                    if iap_sub_cmd == 'u':  # upload code response
                        log_details = f"  解析 [上传代码 'uou']:\n    Continue upload flag: {flag}"
                    elif iap_sub_cmd == 'v':
                        validity = "合法的" if flag == '0' else "非法的"
                        log_details = f"  解析 [主程序校验 'uov']:\n    主程序是{validity}"
                    elif iap_sub_cmd in 'bdt':  # IAP status
                        if flag == '0':
                            # C#通过位移手动解析大端数据
                            caddr, totallen, ccount = struct.unpack_from('>3I', app_payload, 4)
                            log_details = (f"  解析 [IAP状态 'uo{iap_sub_cmd}']:\n    IAP OK, Continue.\n"
                                           f"    caddr=0x{caddr:08X}, total_len={totallen}, ccount={ccount}")
                        else:
                            log_details = f"  解析 [IAP状态 'uo{iap_sub_cmd}']:\n    IAP程序更新失败, code: {flag}"
                    elif iap_sub_cmd == 'f':
                        if flag == '0':
                            log_details = "  解析 [IAP完成 'uof']:\n    程序更新成功"
                        else:
                            log_details = f"  解析 [IAP完成 'uof']:\n    IAP程序更新失败, code: {flag}"
                    elif iap_sub_cmd == 'j':
                        if flag != '0':
                            log_details = "  解析 [跳转主程序 'uoj']:\n    跳转到主程序失败"
                        else:
                            log_details = "  解析 [跳转主程序 'uoj']:\n    跳转成功 (无响应为正常)"
                    elif iap_sub_cmd == 'i':
                        # C#通过位移手动解析，但字节顺序是反的(小端)
                        addr, length, check = struct.unpack_from('<3I', app_payload, 4)
                        log_details = f"  解析 [主代码信息 'uoi']:\n    main code address 0x{addr:08X}, length {length / 1024.0:.2f}kB, check 0x{check:04X}"
                    elif iap_sub_cmd == 'z':
                        validity = "valid" if flag == '1' else "invalid"
                        log_details = f"  解析 [UID有效性 'uoz']:\n    UID is {validity}"

            # ---------------------------------------------------------------------
            # switch (data[3]) -> case (byte)'F': (Plot config)
            # ---------------------------------------------------------------------
            elif main_cmd == 'F':
                plot_sub_cmd = chr(app_payload[1])
                if plot_sub_cmd in 'rs':
                    # 此处省略了更新UI控件的代码
                    log_details = f"  解析 [绘图配置 'Fa{plot_sub_cmd}']:\n    Plot configs {'read' if plot_sub_cmd == 'r' else 'set'} done."
                elif plot_sub_cmd == 'e':
                    en = app_payload[3] > 0
                    log_details = f"  解析 [绘图使能 'Fe']:\n    Plot {'start' if en else 'stop'}"
                elif plot_sub_cmd == 's' and chr(app_payload[2]) == 'a':
                    log_details = "  解析 [绘图自动发送 'Fsa']:\n    Plot auto send enabled"
                elif plot_sub_cmd == 't':
                    enabled = app_payload[2] > 0
                    log_details = f"  解析 [绘图自动发送 'Ft']:\n    Plot auto send {'enable' if enabled else 'disable'}"

            # ---------------------------------------------------------------------
            # switch (data[3]) -> case (byte)'f': (Plot data)
            # ---------------------------------------------------------------------
            elif main_cmd == 'f':
                try:
                    index = app_payload[1]
                    blen = app_payload[2]
                    flen = blen // 4
                    # 注意：C#代码中会根据UI选项判断数据是UInt32还是float
                    # 这里我们假设它总是float，因为这是更通用的情况
                    fdata = struct.unpack_from(f'<{flen}f', app_payload, 3)
                    log_details = f"  解析 [绘图数据 'f']:\n    收到绘图通道 {index} 的数据, 共 {flen} 个点."
                    # for val in fdata:
                    #     log_details += f"{val:.3f}, "
                except (struct.error, IndexError):
                    log_details = "  解析 [绘图数据 'f'] 失败: 数据格式或长度错误"

            # ---------------------------------------------------------------------
            # 未知命令
            # ---------------------------------------------------------------------
            else:
                log_details = f"  收到未知主命令: '{main_cmd}' (Hex: {ord(main_cmd):02X})"

            # 将解析结果显示在UI上
            if log_details:
                self.log_message(log_details.strip(), "darkgreen")
        except Exception as e:
            self.log_message(f"解析数据时发生严重错误: {e}\n  原始负载: {app_payload.hex(' ').upper()}", "red")

    def closeEvent(self, event):
        """重写窗口关闭事件，确保线程和串口被正确关闭"""
        if self.serial_port.is_open:
            self.connect_button.setChecked(False)  # 触发断开
            self.connect_disconnect(False)
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = SerialProtocolApp()
    window.show()
    sys.exit(app.exec())